package tasks.io.core;


import tasks.io.core.Packet.Meta;
import tasks.io.core.lock.SetWithLock;
import tasks.io.core.prop.MapWithLockPropSupport;
import tasks.io.core.stat.ChannelStat;
import tasks.io.core.task.DecodeTask;
import tasks.io.core.task.HandlerTask;
import tasks.io.core.task.SendTask;

import java.io.IOException;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.HashSet;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public abstract class ChannelContext extends MapWithLockPropSupport {

    private static final String DEFAULT_ATTUBITE_KEY = "t-io-d-a-k";
    public static final String UNKNOWN_ADDRESS_IP = "$UNKNOWN";
    public static final AtomicInteger UNKNOWN_ADDRESS_PORT_SEQ = new AtomicInteger();
    public boolean isReconnect = false;
    /**
     * 解码出现异常时，是否打印异常日志
     * 此值默认与org.tio.core.TioConfig.logWhenDecodeError保持一致
     */
    public boolean logWhenDecodeError = false;
    /**
     * 此值不设时，心跳时间取org.tio.core.TioConfig.heartbeatTimeout
     * 当然这个值如果小于org.tio.core.TioConfig.heartbeatTimeout，定时检查的时间间隔还是以org.tio.core.TioConfig.heartbeatTimeout为准，只是在判断时用此值
     */
    public Long heartbeatTimeout = null;
    /**
     * 一个packet所需要的字节数（用于应用告诉框架，下一次解码所需要的字节长度，省去冗余解码带来的性能损耗）
     */
    public Integer packetNeededLength = null;
    public TioConfig tioConfig = null;
    public DecodeTask decodeRunnable = null;
    public HandlerTask handlerRunnable = null;
    public SendTask sendRunnable = null;
    public final ReentrantReadWriteLock closeLock = new ReentrantReadWriteLock();
    private ReadCompletionHandler readCompletionHandler = null;                                            //new ReadCompletionHandler(this);
    public WriteCompletionHandler writeCompletionHandler = null;                                            //new WriteCompletionHandler(this);


    public boolean isWaitingClose = false;
    public boolean isClosed = true;
    public boolean isRemoved = false;
    public boolean isVirtual = false;

    public final ChannelStat channelStat = new ChannelStat();

    public AsynchronousSocketChannel asynchronousSocketChannel;
    private String id = null;
    private Node clientNode;

    private Node serverNode;
    /**
     * 该连接在哪些组中
     */
    private SetWithLock<String> groups = null;
    private Integer readBufferSize = null;                                            //个性化readBufferSize
    public CloseMetaData closeMetaData = new CloseMetaData();
    private CloseCode closeCode = CloseCode.INIT_STATUS;                        //连接关闭的原因码


    public ChannelContext(TioConfig tioConfig, AsynchronousSocketChannel asynchronousSocketChannel) {
        super();
        init(tioConfig, asynchronousSocketChannel);


    }

    /**
     * 创建一个虚拟ChannelContext，主要用来模拟一些操作，譬如压力测试，真实场景中用得少
     *
     * @param tioConfig
     */
    public ChannelContext(TioConfig tioConfig) {
        this(tioConfig, java.util.UUID.randomUUID().toString());
    }

    /**
     * 创建一个虚拟ChannelContext，主要用来模拟一些操作，譬如压力测试，真实场景中用得少
     *
     * @param tioConfig
     * @param id        ChannelContext id
     * @author tanyaowu
     */
    public ChannelContext(TioConfig tioConfig, String id) {
        isVirtual = true;
        this.tioConfig = tioConfig;
        Node clientNode = new Node("127.0.0.1", 26254);
        this.clientNode = clientNode;
        this.id = id;
        if (id == null) {
            this.id = java.util.UUID.randomUUID().toString();
        }

        initOther();
    }

    protected void assignAnUnknownClientNode() {
        setClientNode(createUnknowNode());
    }

    public static Node createUnknowNode() {
        return new Node(UNKNOWN_ADDRESS_IP, UNKNOWN_ADDRESS_PORT_SEQ.incrementAndGet());
    }

    /**
     * 创建Node
     */
    public abstract Node createClientNode(AsynchronousSocketChannel asynchronousSocketChannel) throws IOException;


    /**
     * 等价于：getAttribute(DEFAULT_ATTUBITE_KEY)
     *
     * @return
     * @deprecated 建议使用get()
     */
    public Object getAttribute() {
        return get();
    }

    /**
     * 等价于：getAttribute(DEFAULT_ATTUBITE_KEY)<br>
     * 等价于：getAttribute()<br>
     *
     * @return
     */
    public Object get() {
        return get(DEFAULT_ATTUBITE_KEY);
    }

    /**
     * @return the remoteNode
     */
    public Node getClientNode() {
        return clientNode;
    }

    public SetWithLock<String> getGroups() {
        return groups;
    }


    public String getId() {
        return id;
    }


    public ReadCompletionHandler getReadCompletionHandler() {
        return readCompletionHandler;
    }

    /**
     * @return the serverNode
     */
    public Node getServerNode() {
        return serverNode;
    }

    /**
     * @return the writeCompletionHandler
     */
    public WriteCompletionHandler getWriteCompletionHandler() {
        return writeCompletionHandler;
    }


    public void init(TioConfig tioConfig, AsynchronousSocketChannel asynchronousSocketChannel) {
        id = java.util.UUID.randomUUID().toString();
        this.setTioConfig(tioConfig);

        this.setAsynchronousSocketChannel(asynchronousSocketChannel);
        this.readCompletionHandler = new ReadCompletionHandler(this);
        this.writeCompletionHandler = new WriteCompletionHandler(this);
        this.logWhenDecodeError = tioConfig.logWhenDecodeError;

        initOther();
    }

    void initOther() {
        if (!tioConfig.isShortConnection) {
            //在长连接中，绑定群组几乎是必须要干的事，所以直接在初始化时给它赋值，省得在后面做同步处理
            groups = new SetWithLock<String>(new HashSet<>());
        }
    }


    public void processAfterSent(Packet packet, Boolean isSentSuccess) {
        isSentSuccess = isSentSuccess != null && isSentSuccess;
        Meta meta = packet.getMeta();
        if (meta != null) {
            CountDownLatch countDownLatch = meta.getCountDownLatch();
            //			traceBlockPacket(SynPacketAction.BEFORE_DOWN, packet, countDownLatch, null);
            countDownLatch.countDown();
        }

        try {


            //非SSL or SSL已经握手
            {
                if (tioConfig.getTioListener() != null) {
                    try {
                        tioConfig.getTioListener().onAfterSent(this, packet, isSentSuccess);
                    } catch (Exception e) {
                        //log.error(e.toString(), e);
                    }
                }

                if (tioConfig.statOn) {
                    tioConfig.groupStat.sentPackets.incrementAndGet();
                    channelStat.sentPackets.incrementAndGet();
                }


            }
        } catch (Throwable e) {
            //log.error(e.toString(), e);
        }

        if (packet.getPacketListener() != null) {
            try {
                packet.getPacketListener().onAfterSent(this, packet, isSentSuccess);
            } catch (Throwable e) {
                //log.error(e.toString(), e);
            }
        }

    }

    /**
     * @param asynchronousSocketChannel the asynchronousSocketChannel to set
     */
    public void setAsynchronousSocketChannel(AsynchronousSocketChannel asynchronousSocketChannel) {
        this.asynchronousSocketChannel = asynchronousSocketChannel;

        if (asynchronousSocketChannel != null) {
            try {
                Node clientNode = createClientNode(asynchronousSocketChannel);
                setClientNode(clientNode);
            } catch (IOException e) {
                //log.info(e.toString(), e);
                assignAnUnknownClientNode();
            }
        } else {
            assignAnUnknownClientNode();
        }
    }

    /**
     * 等价于：setAttribute(DEFAULT_ATTUBITE_KEY, value)<br>
     * 仅仅是为了内部方便，不建议大家使用<br>
     *
     * @param value
     * @author tanyaowu
     * @deprecated 不建议各位同学使用这个方法，建议使用set("name1", object1)
     */
    public void setAttribute(Object value) {
        set(value);
    }

    /**
     * 等价于：set(DEFAULT_ATTUBITE_KEY, value)<br>
     * 等价于：setAttribute(Object value)<br>
     *
     * @param value
     * @deprecated 不建议各位同学使用这个方法，建议使用set("name1", object1)
     */
    public void set(Object value) {
        set(DEFAULT_ATTUBITE_KEY, value);
    }


    public void setClientNode(Node clientNode) {
        if (!this.tioConfig.isShortConnection) {
            if (this.clientNode != null) {
                tioConfig.clientNodes.remove(this);
            }
        }

        this.clientNode = clientNode;
        if (this.tioConfig.isShortConnection) {
            return;
        }

        if (this.clientNode != null && !Objects.equals(UNKNOWN_ADDRESS_IP, this.clientNode.getIp())) {
            tioConfig.clientNodes.put(this);
            //			clientNodeTraceFilename = StrUtil.replaceAll(clientNode.toString(), ":", "_");
        }
    }

    /**
     * @param isClosed the isClosed to set
     */
    public void setClosed(boolean isClosed) {
        this.isClosed = isClosed;
        if (isClosed) {
            if (clientNode == null || !UNKNOWN_ADDRESS_IP.equals(clientNode.getIp())) {
                String before = this.toString();
                assignAnUnknownClientNode();
                //log.info("关闭前{}, 关闭后{}", before, this);
            }
        }
    }

    /**
     * @param tioConfig the tioConfig to set
     */
    public void setTioConfig(TioConfig tioConfig) {
        this.tioConfig = tioConfig;

        if (tioConfig != null) {
            decodeRunnable = new DecodeTask(this, tioConfig.tioExecutor);
            handlerRunnable = new HandlerTask(this, tioConfig.tioExecutor);
            sendRunnable = new SendTask(this, tioConfig.tioExecutor);
            tioConfig.connections.add(this);
        }
    }

    public void setPacketNeededLength(Integer packetNeededLength) {
        this.packetNeededLength = packetNeededLength;
    }

    public void setReconnect(boolean isReconnect) {
        this.isReconnect = isReconnect;
    }

    /**
     * @param isRemoved the isRemoved to set
     */
    public void setRemoved(boolean isRemoved) {
        this.isRemoved = isRemoved;
    }

    /**
     * @param serverNode the serverNode to set
     */
    public void setServerNode(Node serverNode) {
        this.serverNode = serverNode;
    }


    public TioConfig getTioConfig() {
        return tioConfig;
    }

    /**
     * 是否是服务器端
     *
     * @return
     * @author tanyaowu
     */
    public abstract boolean isServer();

    /**
     * @return the heartbeatTimeout
     */
    public Long getHeartbeatTimeout() {
        return heartbeatTimeout;
    }

    /**
     * @param heartbeatTimeout the heartbeatTimeout to set
     */
    public void setHeartbeatTimeout(Long heartbeatTimeout) {
        this.heartbeatTimeout = heartbeatTimeout;
    }

    public Integer getReadBufferSize() {
        if (readBufferSize != null && readBufferSize > 0) {
            return readBufferSize;
        }
        return this.tioConfig.getReadBufferSize();
    }

    public void setReadBufferSize(Integer readBufferSize) {
        this.readBufferSize = Math.min(readBufferSize, TcpConst.MAX_DATA_LENGTH);
    }

    public CloseCode getCloseCode() {
        return closeCode;
    }

    public void setCloseCode(CloseCode closeCode) {
        this.closeCode = closeCode;
    }

}
